(function (window) {
    let zQuery = function (selector, context) {
            return new zQuery.fn.init(selector, context);
        },
        logo = 'zQuerymemeda',

        class2type = {},

        toString = class2type.toString,

        hasOwn = class2type.hasOwnProperty,

        isFlatObj = function (obj) {
            if (typeof obj === 'object') {
                for (let i in obj) {
                    if (typeof obj[i] === 'object') {
                        return false;
                    }
                }
            }
            return true;
        },
        isEmptyObj = function (obj) {
            for (let item in obj) {
                return false;
            }
            return true;
        },
        isWindow = function (obj) {
            // window有一个属性window指向自身
            return obj != null && obj === obj.window;
        },

        // 事件派发的时候检查节点和选择器字符串是否匹配
        matchElement = function (node, selector) {
            let elClassName = (node || {}).className;

            // '.class' -> 'class'
            selector = selector ? selector.substr(1) : selector;

            return elClassName.includes(selector);
        },
        objLen = function (obj) {
            let len = 0;
            for (let item in obj) {
                if (obj.hasOwnProperty(item)) {
                    len++;
                }
            }
            return len;
        };

    zQuery.FlatObj = isFlatObj;
    zQuery.isEmptyObj = isEmptyObj;
    zQuery.isWindow = isWindow;
    zQuery.matchElement = matchElement;
    zQuery.objLen = objLen;

    zQuery.fn = zQuery.prototype = {
        constructor: zQuery,

        zQuery: logo,

        eq: function (i) {
            let len = this.length;
            i = i < 0 ? +i + len : i;

            return this.pushStack(i >= 0 && i < len ? [this[i]] : []);
        },

        first: function () {
            return this.eq(0);
        },

        index: function (elem) {

            let count = 0,
                prevSibling;

            // 没传参数
            if (!elem) {
                prevSibling = this.first()[0].previousElementSibling;
                while (prevSibling !== null) {
                    count++;
                    prevSibling = prevSibling.previousElementSibling;
                }
                return count;
            }

            // 传入的字符串用jQuery包装一下
            if (typeof elem === 'string') {
                return Array.prototype.indexOf.call(zQuery(elem));
            }

            // 剩下就是jQuery对象/dom元素集合
            return (
                Array.prototype.indexOf.call(this,
                    elem && elem.zQuery ? elem[0] : elem
                ));
        },

        /*
         *   将传入的元素放入jQuery对象
         *   将原来的元素作为prevObject保存
        */
        pushStack: function (elems) {
            let ret;
            ret = zQuery.merge(this.constructor(), elems);

            ret.prevObject = elems;

            return ret;
        },

        find: function (elem) {
            let context = this[0] || document;

            if (!elem) {
                return this;
            }

            if (typeof elem === 'string') {
                return zQuery(elem, context);
            }
        },

        is: function (status) {
            let elem = this[0];

            // :hidden :shown
            if (typeof status === 'string') {
                if (status === ':hidden' && (zQuery.css(this, 'display') === 'none' || elem.style.display === 'none')) {
                    return true;
                }
                if (status === ':shown' && (zQuery.css(this, 'display') !== 'none' || elem.style.display !== 'none')) {
                    return true;
                }
            }
            return false;
        },

        html: function (val) {
            let elem;

            for (let key in this) {
                elem = this[key];

                if (elem && elem.nodeType) {
                    if (val) {
                        elem.innerHTML = val;
                    } else {
                        return elem.innerHTML;
                    }
                }
            }
            return this;
        },

        val: function (val) {
            let elem = this[0];

            if (elem) {
                if (val) { // set
                    elem.value = val;
                } else { // get
                    return elem.value;
                }
            }

            return '';
        }
    };

    // 1. $.fn.extend({}) 扩展jQuery本身
    // 2. $.fn.extend(true, {}, {}) 深拷贝
    // 3. $.fn.extend({}, {}) 浅拷贝
    zQuery.extend = zQuery.fn.extend = function () {
        let length = arguments.length,
            i = 1,
            options,
            name,
            copy,
            deep = false,
            target = arguments[0];

        if (typeof target === 'boolean') {
            deep = target;

            target = arguments[i];
            i++;
        }

        if (typeof target !== 'object') {
            target = {};
        }

        // 扩展$.fn/$
        if (i === length) {
            target = this;
            i--;
        }

        for (; i < length; i++) {
            // 遍历传入的每一个对象
            if ((options = arguments[i]) !== null) {

                // 遍历传入对象的每一个属性
                // 这里以options的属性为主导，添加/覆盖target上的属性
                for (name in options) {
                    // 被拷贝的属性值
                    copy = options[name];

                    if (target === copy) {
                        continue;
                    }

                    // 拷贝和被拷贝值都得是对象才进入深拷贝
                    if (deep && copy && (!isFlatObj(copy) && !isFlatObj(target[name]))) {
                        target[name] = zQuery.extend(deep, target[name], copy);
                    } else if (copy != undefined) {
                        target[name] = copy;
                    }
                }
            }
        }
        // 返回深拷贝结束的target[ name ]
        return target;
    }


    zQuery.extend({
        each: function (obj, callback, args) {
            let length, i = 0;

            // 如果是数组或者类数组
            if (isArrayLike(obj)) {
                length = obj.length;
                for (; i < length; i++) {
                    // 调用callback 传入obj的每一项属性值item 以及索引index
                    if (callback.call(obj[i], i, obj[i]) === false) {
                        // if return false 就终止循环
                        break;
                    }
                }
            } else {
                // 对象就for in 循环
                for (i in obj) {
                    if (callback.call(obj[i], i, obj[i]) === false) {
                        break;
                    }
                }
            }
            return obj;
        },

        // 将each跟forEach结合 让forEach能遍历对象,并且舍去原型上的属性
        zorEach: function (obj, callback, needOwn = true) {
            let length, i = 0, ret;

            // 如果是数组或者类数组
            if (isArrayLike(obj)) {
                length = obj.length;
                for (; i < length; i++) {
                    // 调用callback 传入obj的每一项属性值item 以及索引index
                    ret = callback.call(obj[i], obj[i], i);
                    if (ret === false) {
                        // if return false 就终止循环
                        break;
                    } else if (ret === "continue") {
                        // return "continue"
                        continue;
                    }
                }
            } else {
                // 对象就for in 循环
                for (i in obj) {

                    if (needOwn && !hasOwn.call(obj, i)) continue;

                    // zorEach({}, (key, value)
                    ret = callback.call(obj[i], i, obj[i]);

                    if (ret === false) {
                        // if return false 就终止循环
                        break;
                    } else if (ret === "continue") {
                        // return "continue"
                        continue;
                    }
                }
            }

            return obj;
        },

        merge: function (first, second) {
            let length = second.length,
                i = first.length,
                j;

            for (j = 0; j < length; j++) {
                first[i++] = second[j];
            }

            first.length = i;

            return first;

        }
    });


    let init = zQuery.fn.init = function (selector, context) {
            let nodeList,
                i = 0;

            context = context || document;

            // $() 相当于调用原型方法
            if (!selector) {
                this.length = 0;
                return this;
            }

            // window / document
            if (selector === document || selector === window) {
                this.length = 1;
                this[i] = selector;
                return this;
            }


            // $($(el))
            if (selector.zQuery) {
                return selector;
            }

            // dom元素 仅支持单个dom元素
            if (selector.nodeType === 1) {
                this.length = 1;
                this[i] = selector;
                return this;
            }

            // .class #id tag
            if (typeof selector === 'string') {

                nodeList = context.querySelectorAll(selector);

                while (nodeList[i]) {
                    this[i] = nodeList[i];
                    i++;
                }

                this['length'] = i;

                return this;
            }


        },
        isArrayLike = function (obj) {
            let length = obj.length;

            if (!obj || !('length' in obj)) {
                return false;
            }

            return Array.isArray(obj) || !!(typeof obj === 'object' && obj[length - 1]);

        },
        dataAttr = function (elem, key, data) {
            let name;

            if (elem.nodeType === 1 && data === undefined) {
                if (typeof key === 'string') {
                    name = 'data-' + key;
                    data = elem.getAttribute(name);

                    // set data to cache
                    dataUser.set(elem, key, data);
                }
            } else {
                data = undefined;
            }

            return data;
        };

    init.prototype = zQuery.fn;

    zQuery.fn.extend({
        each: function (callback, args) {
            return zQuery.each(this, callback, args);
        },
        zorEach: function (callback, args) {
            return zQuery.zorEach(this, callback, args);
        },
        on: function (types, selector, fn) {
            return on(this, types, selector, fn);
        },
        one: function (types, selector, fn) {
            return on(this, types, selector, fn, 1);
        },
        off: function (types, selector, fn) {
            let handleObj;

            // 传入的是事件对象
            if (typeof types === 'object' && types.handleObj) {
                handleObj = types.handleObj;

                $(types.delegateTarget).off(
                    handleObj.type,
                    handleObj.selector,
                    handleObj.handler
                )
            }

            return this.each(function () {
                zQuery.event.remove(this, types, selector, fn);
            })
        },
        trigger: function (type, data) {
            this.each(function () {
                zQuery.event.trigger(this, type, data);
            })
        },
        hover: function (fnOver, fnOut) {
            return this.mouseenter(fnOver).mouseleave(fnOut || fnOver);
        }
    });

    zQuery.each(("blur focus focusin focusout resize scroll click dblclick " +
        "mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " +
        "change select submit keydown keypress keyup contextmenu").split(" "),
        function (_i, name) {

            // Handle event binding
            // $().click()...
            zQuery.fn[name] = function (data, fn) {
                return arguments.length > 0 ?
                    this.on(name, null, data, fn) :
                    this.trigger(name);
            };
        });

    function on(elem, types, selector, fn, one) {
        let origFn;

        // (types, fn)
        if (!fn) {
            fn = selector;
            selector = null;
        }

        // one
        if ((fn === 1 && one === undefined) || one === 1) {
            origFn = fn;
            fn = function (event) {
                $().off(event);
                origFn.apply(this, arguments);
            };

            fn.guid = origFn.guid || (origFn.guid = zQuery.guid++)
        }

        // add(elem, type, handle, selector)
        // 支持多个对象绑定事件

        return elem.zorEach(function () {
            zQuery.event.add(this, types, fn, selector);
        })
    }

    /* 缓存模块开始 */
    zQuery.guid = 1;
    zQuery.expando = "zQuery" + ('' + Math.random()).replace(/\D/g, "");

    function Data() {
        this.expando = zQuery.expando + Data.uid++;
    }

    Data.uid = 1;

    Data.prototype = {
        cache: function (owner) {
            let value = owner[this.expando];

            if (!value) {
                value = {};

                if (owner.nodeType || isWindow(owner)) {
                    owner[this.expando] = value;
                }
            }

            return value;
        },
        get: function (owner, key) {
            return key === undefined ?

                this.cache(owner) :

                owner[this.expando] && owner[this.expando][key];
        },
        set: function (owner, data, value) {
            let cache = this.cache(owner),
                prop;

            if (typeof data === 'string') {
                cache[data] = value;

            } else {
                for (prop in data) {
                    cache[prop] = value;
                }
            }

            return cache;
        },
        remove: function (owner, key) {
            let i,
                cache = owner[this.expando];

            if (cache === undefined) {
                return;
            }

            if (key !== undefined) {
                // 'events' 直接使用 'events handle' 分割成数组
                key = key in cache ?
                    [key] :
                    (key.match(/[^ ]+/g) || []);
                i = key.length;

                while (i--) {
                    delete cache[key[i]];
                }


                if (zQuery.isEmptyObj(cache)) {
                    if (owner.nodeType) {
                        delete owner[this.expando];
                    }
                }
            }
        }
    };

    let dataPriv = new Data();

    let dataUser = new Data();
    /* 缓存模块结束 */

    /*let init = {
        elem: {
            jQuery351046541611:{
                events: {
                    // 'click'
                    handlers: [
                        handleObj: {
                            type: type, // 填充类型
                            // fn
                            handler: f(),
                            // handler.guid fn上也保存了id
                            guid: handler.guid
                        }
                    ]
                },
                handle: f( e )
            }
        }
    }*/
    zQuery.event = {
        // 1.创建缓存对象 与 dom对象建立映射（通过添加expando属性）
        // 2.addEventListener
        // 3.将监听的事件派发
        // @params: types -> 'string' 支持绑定多个事件
        add: function (elem, types, handle, selector) {
            let handleObj,
                elemData = dataPriv.get(elem),
                handlers,
                type,
                t;

            // 去掉没有nodeType 也不是window对象的elem
            if (!handle || (!elem.nodeType && !isWindow(elem))) {
                return;
            }

            if (!handle.guid) {
                handle.guid = zQuery.guid++;
            }

            // 创建events
            if (!elemData.events) {
                elemData.events = {};

            }


            // 创建handle
            if (!(eventHandle = elemData.handle)) {
                eventHandle = elemData.handle = function (e) {

                    return zQuery.event.dispatch.apply(elem, arguments);
                };
            }

            types = types.indexOf(' ') ? types.split(' ') : ([types] || ['']);

            t = types.length;


            while (t--) {

                type = types[t] || '';
                // 创建存放数据的数组

                if (!type) {
                    continue;
                }

                // 在这里做修正，mouseenter mouseleave无法完成事件冒泡
                if (type === 'mouseenter' && selector) {
                    type = 'mouseover';
                }

                if (type === 'mouseleave' && selector) {
                    type = 'mouseout';
                }


                if (!(handlers = elemData.events[type])) {
                    // 初始化events
                    handlers = elemData.events[type] = [];
                    handlers.delegateCount = 0;

                    if (elem.addEventListener) {
                        elem.addEventListener(type, eventHandle);
                    }
                }

                // 创建数组中要存放的事件相关信息
                // handleObj = {
                //     type: type,
                //     handler: handle,
                //     guid: handle.guid,
                //     selector: 被代理的对象
                // };
                handleObj = {
                    type: type,
                    handler: handle,
                    guid: handle.guid,
                    selector: selector
                };

                // 确保有事件代理的事件先进入数组
                if (selector) {
                    handlers.splice(handlers.delegateCount++, 0, handleObj);
                } else {
                    handlers.push(handleObj);
                }

            }

            return this;
        },

        // zQuery.event.off(this, types, selector, fn);
        // handlers 是传入回调的包装对象
        // handlers[j].handler 就是传入的回调
        remove: function (elem, types, selector, fn) {

            let i,
                events,
                t,
                type,
                handlers,
                elemData = dataPriv.get(elem);


            if (!fn) {
                fn = selector;
            }

            if (!elemData || !(events = elemData.events)) {
                return;
            }

            types = (types || '').match(/[^ ]+/g) || [''];
            t = types.length;

            while (t--) {
                type = types[t];
                handlers = events[type] || [];

                j = handlers.length;

                while (j--) {

                    if ((fn && fn.guid === handlers[j].guid)) {
                        handlers.splice(j, 1);

                        if (handlers.selector) {
                            handlers.delegateCount--;
                        }

                        if (zQuery.isEmptyObj(handlers)) {
                            delete events[type];
                        }
                    }
                }

                //  在这里移除浏览器监听的handle
                if (elem && elemData.handle && type) {
                    elem.removeEventListener(type, elemData.handle);
                }
            }


            if (zQuery.isEmptyObj(events)) {
                dataPriv.remove(elem, 'handle events');
            }
        },

        dispatch: function (nativeEvent) {
            let ret,
                i,
                j,
                matched,
                handlerQueue,
                handleObj,

                handlers = (
                    dataPriv.get(this, "events") || Object.create(null)
                )[nativeEvent.type] || [];

            // 修正原生事件对象
            event = zQuery.event.fix(nativeEvent);

            // 委托对象
            event.delegateTarget = this;

            // 被代理对象（string)
            event.selector = handlers[0] && handlers[0].selector;

            handlerQueue = zQuery.event.handlers.call(this, event, handlers);


            i = 0;

            while ((matched = handlerQueue[i++]) && !event.isPropagationStopped) {

                for (j = 0; j < matched.handlers.length; j++) {
                    handleObj = matched.handlers[j];
                    // 在event上装载handleObj
                    event.handleObj = handleObj;

                    // 执行handler,改变this
                    ret = handleObj.handler.apply(matched.elem, arguments);

                }
            }
            return ret;
        },

        trigger: function (elem, type, data) {
            let handle = dataPriv.get(elem) && dataPriv.get(elem).handle,
                event = {};

            if (type === 'submit') {
                debugger;
            }
            if (elem.zQuery) {
                elem = elem[0];
            }
            event.type = type;

            data = [event].concat(data);

            if (handle) {
                handle.apply(elem, data);
            }
        },

        handlers: function (event, handlers) {
            var i,
                cur,
                sel,
                handleQueue = [],
                handleObj,
                isMatched,
                matchHandler,
                delegateCount = handlers.delegateCount;

            cur = event.target || {};

            if (delegateCount && cur.nodeType) {

                // 向上遍历每个元素节点
                for (; cur !== this; cur = cur.parentNode || this) {

                    if (cur.nodeType === 1) {
                        matchHandler = [];

                        for (i = 0; i < delegateCount; i++) {
                            handleObj = handlers[i];
                            sel = handleObj.selector;
                            isMatched = zQuery.matchElement(cur, sel);


                            if (isMatched) {
                                matchHandler.push(handleObj);
                            }
                        }

                        // 每匹配完一个就先装载事件，保证顺序
                        if (matchHandler.length) {
                            handleQueue.push({elem: cur, handlers: matchHandler});
                        }
                    }
                }
            }

            // 开始处理元素本身绑定的事件
            cur = this;
            if (handlers.length > delegateCount) {
                handleQueue.push({elem: cur, handlers: handlers.slice(delegateCount)})
            }

            return handleQueue;
        },

        fix: function (nativeEvent) {
            nativeEvent.stopPropagation = function () {
                this.isPropagationStopped = true;
            };
            return nativeEvent;
        }
    };

    // $().data() 自定义属性
    zQuery.fn.extend({
        data: function (key, value) {
            let elem = this[0],
                name,
                data,
                i,
                attr = elem && elem.attributes;

            // get All value
            // $().data()
            if (elem && key === undefined) {
                data = {};
                i = attr.length;
                while (i--) {
                    if (attr[i]) {
                        name = attr[i].name;

                        // data-xxx="aaa" -> {active: "aaa"}
                        if (name && name.indexOf('data-') === 0) {
                            // data['xxx'] = elem.getAttribute("data-xxx")
                            data[name.slice(name.indexOf('-') + 1)] = elem.getAttribute(name);
                        }
                    }
                }
                return data;
            }

            // get value from html5 custom data attr or cache
            // $(el).data(key)
            if (elem && value === undefined) {

                // 先从缓存中获取
                data = dataUser.get(elem, key);

                if (data !== undefined) {
                    return data;
                }

                // 再从标签中获取
                data = dataAttr(elem, key);

                if (data !== undefined) {
                    return data;
                }
            }

            // set data
            // $(el).data(key, value)
            return this.each(function () {
                if (this.nodeType) {
                    dataUser.set(this, key, value);
                }
            })
        },

        removeData: function (key) {
            return this.each(function () {
                dataUser.remove(this, key);
            })
        }
    });

    /*
    * ajax 开始
    * */
    zQuery.extend({
        ajaxDefaultSettings: {
            method: 'GET',
            data: null,
            dataType: 'JSON',
            async: true,
            cache: true
        },

        ajax: function (options) {
            return new zQuery.ajax.prototype.init(options);
        },

        getJSON: function (options) {
            if (typeof options === 'string') {
                let reviseOptions = {};
                reviseOptions.url = options;
                options = reviseOptions;
            }

            return zQuery.ajax($.extend(options, {
                dataType: 'JSON'
            }));
        }
    });
    zQuery.ajax.prototype = {
        constructor: zQuery.ajax,
        init: function (options) {
            let {
                url,
                method = 'get',
                data = null,
                dataType = 'JSON',
                async,
                cache,
                success,
                error
            } = $.extend(zQuery.ajaxDefaultSettings, options);

            // 配置挂载mount
            $.zorEach(['url', 'method', 'data', 'dataType', 'async', 'cache', 'success', 'error'], (item) => {
                this[item] = eval(item);
            });

            return this.send();
        },
        /*
        *
        * send request
        * */
        send: function () {
            this.handleData();
            this.handleCache();

            let {
                    url, method, async, data, dataType, success, error
                } = this,
                xhr = new XMLHttpRequest();

            // 跨域不能直接通过ajax获取数据
            if (dataType.toLowerCase() === 'jsonp') {
                this.zQuery_promise = jsonpGetData(url);
            } else {
                this.zQuery_promise = new Promise((resolve, reject) => {

                    xhr.open(method, url, async);
                    xhr.onreadystatechange = () => {

                        if (xhr.readyState === 4) {
                            // success
                            if (/^2\d{2}$/.test(xhr.status)) {
                                let ret = this.handleDataType(url, xhr);
                                success && success(ret);
                                resolve(ret);

                                console.log('successfully! data is loaded by ajax');
                            }

                            // fail
                            // 如果不是以2/3开头的状态码就认定为失败
                            if (!/^([23])\d{2}$/.test(xhr.status)) {
                                error && error();
                                reject();

                                console.log('fail to get data by ajax');
                            }
                        }
                    }
                    ;
                    xhr.send(data);
                });
            }


            return this;
        },

        handleCache: function () {
            let {url, method, cache} = this,
                timeStamp = `_=${+(new Date())}`;

            // 不需要缓存就在后面+时间戳，保证每次url都不重复
            if (/^get$/i.test(method) && cache === false) {
                // xx.xx?x=1 -> xx.xx?x=1&
                // xx.xx -> xx.xx?
                url += url.indexOf('?') > -1 ? '&' : '?';
                url += timeStamp;
                this.url = url;
            }
        },

        handleData: function () {
            let {method, url, data} = this,
                getReg = /^(get|head|options|delete|trace)$/i,
                formatedData = url;

            // null
            if (!data) {
                this.data = null;
                return;
            }

            formatedData += url.indexOf('?') > -1 ? '&' : '?';

            if (typeof data === 'object' && getReg.test(method)) {
                zQuery.zorEach(data, (key, value) => {
                    formatedData += `${key}=${value}`;
                });
                this.url = formatedData;
                this.data = null;
            }
        },

        handleDataType: function (url, xhr) {
            let dataType = this.dataType.toUpperCase(),
                result = xhr.responseText;

            if (dataType === 'JSON') {
                result = JSON.parse(result);
            } else if (dataType === 'XML') {
                result = xhr.responseXML;
            }

            // default: 'TEXT'
            return result;
        },

        done: function (callback) {
            this.zQuery_promise.then((resolve) => {
                callback(resolve);
            });

            return this;
        },
        fail: function (callback) {
            this.zQuery_promise.catch(() => {
                callback();
            })

            return this;
        },

        always: function (callback) {
            this.zQuery_promise.finally(() => {
                callback();
            })
        }
    };

    // promise管控发起jsonp
    const rUrlCallback = /callback=([a-zA-Z]+)/ig;
    zQuery.jsonpResolve = null;

    function jsonpGetData(url) {

        return new Promise((resolve) => {
            let tempScript = document.createElement('script');

            if (url) {
                url = url.replace(/callback=([^&])+/ig, (...args) => {
                    return 'callback=zQuery.jsonpResolve';
                });
            } else {
                console.log("url is empty");
                return url;
            }

            tempScript.src = url;
            document.body.appendChild(tempScript);

            zQuery.jsonpResolve = function (res) {
                // 去掉临时的script标签
                document.body.removeChild(tempScript);
                tempScript = null;

                resolve(res);
            }
        })

    }

    zQuery.ajax.prototype.init.prototype = zQuery.ajax.prototype;
    /*
    * ajax 结束
    * */

    /*
    * 盒子模型 开始
    *
    * */
    zQuery.each({scrollTop: "pageYOffset", scrollLeft: "pageXOffset"}, function (method, prop) {
        let top = prop === "pageYOffset";

        zQuery.fn[method] = function (val) {
            let elem = this[0];

            if (typeof elem === 'object') {

                let win;
                // window document 一并处理
                if (isWindow(elem)) {
                    win = elem;
                } else if (elem.nodeType === 9) {
                    win = elem.defaultView;
                }

                // get or set
                if (!val) {
                    return win ? win[prop] : elem[method];
                }

                if (win) {
                    win.scrollTo(
                        !top ? val : win.pageXOffset,
                        top ? val : win.pageYOffset
                    )
                } else {
                    elem[method] = val;
                }
            }
        }
    })
    zQuery.fn.extend({
        offset: function () {
            let elem = this[0],
                rect,
                win;

            if (!elem) {
                return;
            }

            win = elem.ownerDocument.defaultView;
            rect = elem.getBoundingClientRect();

            return {
                top: rect.top + win.pageYOffset,
                left: rect.left + win.pageXOffset
            }
        }
    });
    /*
    * 盒子模型 结束
    * */


    /*css 模块*/
    zQuery.extend({
        /* getCssStyleValue */
        css: function (elems, name) {
            let elem = elems[0],
                win,
                val,
                ndName,
                RmatchNum = /^\d+(\.\d+)?/g;

            if (!elem) {
                return;
            }

            // window不能用getComputedStyle,它并不是一个严格意义上的节点
            if (isWindow(elem) && (name === 'height' || name === 'width')) {
                return name === 'height' ? elem.innerHeight : elem.innerWidth;
            }

            win = elem.ownerDocument.defaultView;

            ndName = elem && elem.nodeName.toLowerCase();
            if (ndName === 'html' || ndName === 'body') {
                val = elem[name];
            } else {
                val = win.getComputedStyle(elem, null).getPropertyValue(name);
            }

            // width: 30px -> 30
            // display: none -> none;
            // 返回去掉单位的数字字符串
            val = RmatchNum.test(val) ? (String(val).match(RmatchNum) || [])[0] : val;

            return val;
        },

        /* setCssStyleValue */
        style: function (elem, name, value) {
            let pxTestReg = /^(width|height|left|top|right|bottom|margin|padding|border|fontSize)(-top|left|bottom|right)?/,
                unitReg = /(px)$/,
                ndName;

            if (elem.zQuery) {
                elem = elem[0];
            }

            if (pxTestReg.test(name) && !unitReg.test(value)) {
                value += 'px';
            }

            // 对html body 做兼容处理
            ndName = elem && elem.nodeName.toLowerCase();
            if (ndName && (ndName === 'html' || ndName === 'body')) {
                elem[name] = value;
            } else {
                elem.style[name] = value;
            }

            return value;
        },
        setGroupStyle: function (elems, options) {
            let len = elems.length,
                i = 0;

            for (; i < len; i++) {
                for (let name in options) {
                    zQuery.style(elems[i], name, options[name]);
                }
            }
        }
    });

    zQuery.zorEach({Height: 'height', Width: 'width'}, function (_, type) {
        zQuery.fn[type] = function (value) {
            return value === undefined ? zQuery.css(this, type) : zQuery.style(this, type, value);
        }
    });

    zQuery.fn.extend({
        css: function (name, value) {

            if (!name) {
                return this;
            }

            // set css style
            if (value !== undefined) {
                // $(el).css(name, value)
                zQuery.style(this, name, value);

            } else {
                // get css style
                if (typeof name === 'object' && name !== null) {
                    // $(el).css({name: value})
                    zQuery.setGroupStyle(this, name);
                } else {
                    // $(el).css(name)
                    return zQuery.css(this, name);
                }
            }

            return this;
        }
    });
    /*css 模块结束*/

    /*class 模块*/
    function getClass(className) {
        return className.getAttribute && className.getAttribute('class') || '';
    }

    function classesToArray(classes) {
        return classes.match(/\S+/g) || [];
    }

    function formatClass(curClasses) {
        return curClasses.match(/\S+/g).join(' ');
    }


    zQuery.each({addClass: 'addClass', removeClass: 'removeClass'}, function (_, fnName) {
        zQuery.fn[fnName] = function (value) {
            let classes,
                i,
                elem,
                curClasses;

            if (typeof value !== 'string' || !value) {
                return this;
            }

            // 要添加的classes
            classes = classesToArray(value);

            i = 0;
            while (elem = this[i++]) {
                curClasses = getClass(elem);
                zQuery.each(classes, function (_, item) {

                    if (curClasses.indexOf(item) === -1) {
                        curClasses += fnName === 'addClass' ? ' ' + item : '';

                    } else {
                        curClasses = fnName === 'removeClass' ? curClasses.replace(item, '') : curClasses;
                    }

                });

                curClasses = fnName === 'removeClass' ? formatClass(curClasses) : curClasses;


                elem.setAttribute('class', curClasses);
            }
            return this;
        }
    });

    zQuery.fn.hasClass = function (className) {
        // this:$(els)
        let len = this.length,
            i,
            classGroup;

        if (!len || !className) {
            return false;
        }
        for (i = 0; i < len; i++) {
            classGroup = getClass(this[i]);
            if (classGroup && classGroup.includes(className)) {
                return true;
            }
        }

        return false;
    };
    /*class 模块结束*/

    /* attr */
    zQuery.fn.extend({
        attr: function (name, val) {
            return this.each(function () {
                zQuery.attr(this, name, val);
            })
        },
        removeAttr: function (name) {
            return this.each(function () {
                zQuery.removeAttr(this, name, val);
            })
        }
    });
    zQuery.extend({
        attr: function (elem, name, val) {
            let ndType = elem.nodeType;

            // Don't get/set attributes on text, comment and attribute nodes
            if (!ndType || ndType === 2 || ndType === 3 || ndType === 8) {
                return;
            }

            if (!val) {
                return elem.getAttribute(name, val);
            }
            /*console.log(name);
            console.log(val);
            console.log(elem);*/
            elem.setAttribute(name, val);
        },
        removeAttr: function (elem, name) {

            let ndType = elem.nodeType;
            if (ndType === 2 || ndType === 3 || ndType === 8) {
                return;
            }

            if (name) {
                elem.removeAttribute(name);
            }
        }
    })

    /*proxy*/
    zQuery.proxy = function (fn, context) {
        let proxy,
            args,
            temp;

        if (typeof context === 'string') {
            temp = fn[context];
            context = fn;
            fn = temp;
        }

        if (toString.call(fn) !== '[object Function]') {
            return undefined;
        }

        args = [].slice.call(arguments, 2);
        proxy = function () {
            return fn.apply(context || this, args.concat([].slice.call(arguments)));
        };

        return proxy;
    };

    /* showHide */
    function showHide(elems, show) {
        let elem,
            i,
            j,
            len = elems.length,
            displayVal,
            values = [];

        for (i = 0; i < len; i++) {
            elem = elems[i];

            // hide
            if (!show) {

                if (elem.style.display !== 'none') {

                    // elem.displayVal = elem.style.display;
                    dataPriv.set(elem, 'display', elem.style.display);

                    values[i] = 'none';
                }
            } else { // show
                // 读取缓存
                displayVal = dataPriv.get(elem, 'display');

                // 有缓存用缓存，没有缓存清空value（相当于设置标志位）
                if (elem.style.display === 'none') {
                    values[i] = displayVal ? displayVal : '';
                }

                // 没有缓存，且通过css样式表设置
                if (!values[i] && window.getComputedStyle(elem).getPropertyValue('display') === 'none') {
                    values[i] = 'block';
                }
            }
        }

        for (j = 0; j < len; j++) {
            if (!elems[j]) {
                continue;
            }

            elems[j].style.display = values[j];
        }

        return elems;
    }

    zQuery.fn.extend({
        show: function () {
            return showHide(this, true);
        },
        hide: function () {
            return showHide(this);
        },
        showHide: function (elems, show) {
            let elem,
                i,
                j,
                len = elems.length,
                displayVal,
                values = [];

            for (i = 0; i < len; i++) {
                elem = elems[i];

                // hide
                if (!show) {

                    if (elem.style.display !== 'none') {

                        // elem.displayVal = elem.style.display;
                        dataPriv.set(elem, 'display', elem.style.display);

                        values[i] = 'none';
                    }
                } else { // show
                    // 读取缓存
                    displayVal = dataPriv.get(elem, 'display');

                    // 有缓存用缓存，没有缓存清空value（相当于设置标志位）
                    if (elem.style.display === 'none') {
                        values[i] = displayVal ? displayVal : '';
                    }

                    // 没有缓存，且通过css样式表设置
                    if (!values[i] && window.getComputedStyle(elem).getPropertyValue('display') === 'none') {
                        values[i] = 'block';
                    }
                }
            }

            for (j = 0; j < len; j++) {
                if (!elems[j]) {
                    continue;
                }

                elems[j].style.display = values[j];
            }

            return elems;
        }
    });

    /* 动画模块开始 */
    let timers = [],
        inProgress;

    zQuery.easing = {
        linear: function (p) {
            return p;
        },
        swing: function (p) {
            return 0.5 - Math.cos(p * Math.PI) / 2;
        },
        _default: "swing"
    };

    zQuery.duration = {
        fast: 400,
        slow: 1600,
        normal: 800,
        _default: 800
    };

    // queue
    zQuery.extend({
        queue: function (elem, type, fn) {
            var queue;

            if (elem) {
                type = (type || "fx") + "queue";

                queue = dataPriv.get(elem, type);

                // 没传入data,仅作为查询返回
                if (fn) {
                    if (!queue) {
                        // 注意 仅在"fxqueue"上存在队列
                        queue = dataPriv.set(elem, type, [fn]);
                    } else {
                        queue.push(fn);
                    }
                }
            }
            return queue || [];
        },

        dequeue: function (elem, type) {
            type = type || "fx";

            // 取出队列
            var queue = zQuery.queue(elem, type),
                fn = queue.shift();

            if (fn === "inProgress") {
                fn = queue.shift();
            }

            // 加入"inProgress"标志位，只让第一个入队的函数自动执行
            // 其他只能等到上一个任务结束才能执行
            // 作用主要体现在zQuery.fn.queue中
            // 没有"inProgress" 的函数入队直接dequeue
            if (fn) {
                queue.unshift("inProgress");
                fn.call(elem);
            }
        }
    });
    // 接口 实质上也是处理多个dom元素对象
    zQuery.fn.extend({
        queue: function (type, data) {
            if (typeof type !== "string") {
                data = type;
                type = "fx";
            }

            return data === undefined ?
                this :
                this.each(function () {
                    let elem = this,
                        queue;

                    if (elem.nodeType) {
                        queue = zQuery.queue(elem, type, data);
                    }

                    // 第一个传入的函数直接执行
                    if (type === "fx" && this.nodeType && queue[0] !== 'inProgress') {
                        zQuery.dequeue(this, type);
                    }
                });
        },
        dequeue: function (type) {
            return this.each(function () {
                zQuery.dequeue(this, type);
            })
        }
    });

    zQuery.extend({
        animate: function (elem, options, duration, easing, callback) {
            // 修正传入的参数
            let operate = modifyOptions(options, duration, easing, callback),
                optionsLen = zQuery.objLen(options);


            // 入队，第一次调用的对象该匿名函数会直接执行
            $(elem).queue(function () {
                let elem = this;

                // 遍历每个属性，生成一个fx对象进行管控
                $.zorEach(options, function (name, value) {

                    // 主要是处理fadeIn的特殊情况，不需要生成Fx对象，直接设置"block"
                    if (name === "display" && value === "block" && name === "special") {
                        elem.style.display = "block";
                        return "continue";
                    }

                    let fx = new zQuery.Fx(elem, operate, name, optionsLen),
                        start = parseFloat($(elem).css(name)),
                        end = value;

                    fx.custom(start, end);
                });
            })

        }
    });

    // $(el).animate({opacity:0}, 1000, easing, callback)
    zQuery.fn.extend({
        animate: function (options, duration, easing, callback) {

            $.each(this, function (_, elem) {
                zQuery.animate(elem, options, duration, easing, callback);
            });

            return this;
        },
        stop: function () {
            let i = timers.length - 1;

            // $(elements) 遍历每个elements,找到对应的fx
            // 对象，调用fx对象上的stop
            return this.each(function (_, elem) {
                for (; i >= 0; i--) {
                    if (elem === timers[i].elem) {
                        // 删除elem在timers中的渲染任务
                        timers[i].stop();
                    }
                }

                // 因为已经出现入队现象，队首加了"inProgress"
                // 会导致下次再调用animate不再自动执行匿名函数
                let queue = dataPriv.get(elem, "fxqueue");
                queue[0] === "inProgress" ? queue.shift() : null;

            })
        }
    });


    // 修正传入的参数
    function modifyOptions(options, duration, easing, callback) {

        // animate(options, callback)
        if (typeof duration === 'function') {
            callback = duration;
            duration = undefined;
        } else if (typeof 'easing' === 'function') {
            // animate(options, duration, callback)
            callback = easing;
            easing = undefined;
        }

        // 检查传入的是'slow' 'fast' 这样的字符串？ 转成数字
        duration = typeof duration === 'string' ? zQuery.duration[duration] : duration;

        let obj = {
            duration: duration || zQuery.duration._default,
            easing: easing || zQuery.easing._default
        };

        obj.callback = function () {
            callback && callback();
            $(this).dequeue();
        };

        return obj;
    }

    zQuery.Fx = function (elem, options, name, proCount) {
        this.elem = elem;
        this.options = options;
        this.name = name;

        // 记录传入属性的个数
        this.proCount = proCount;

        // 初始化运动结束个数
        this.options.moveEndCount = 0;
    };


    // 管控动画运动
    zQuery.Fx.prototype.custom = function (from, to) {

        // 记录开始时间
        this.startTime = zQuery.now();

        // 初始值
        this.start = from;

        // 终点值
        this.end = to;

        // 将每个fx对象压入全局的timers动画栈
        timers.push(this);

        // 启动定时器监控动画执行
        zQuery.Fx.start();
    };

    // fx每步执行的方法
    zQuery.Fx.prototype.step = function () {
        let nowTime = zQuery.now(),
            temp,
            nowPos,
            consumed;

        let {duration, easing} = this.options;

        // 如果现在时间超过了开始时间+运动持续时间，运动结束
        if (nowTime > duration + this.startTime) {
            nowPos = this.end;

            // 传入的属性都运动完毕，执行一次回调/
            this.options.moveEndCount++;
            if (this.options.moveEndCount === this.proCount) {
                this.options.callback.call(this.elem);
            }

            this.stop();
        } else {
            consumed = nowTime - this.startTime;
            // 根据消耗时间和持续时间计算出当前位置的比例
            temp = zQuery.easing[easing]((consumed / duration));
            nowPos = this.start + temp * (this.end - this.start);
        }

        // 更新css样式
        this.update(this.name, nowPos);
    };

    // 更新elem元素样式
    zQuery.Fx.prototype.update = function (name, value) {

        $(this.elem).css(name, value);
    };

    // 结束当前fx对象的运动,从timers中移除
    zQuery.Fx.prototype.stop = function () {
        // for (let i = 0; i = timers.length; i++) {
        // 之前的问题就是在这里，写成i = timers.length
        // 导致一直删不去timer,这样就不会触发Fx.stop
        // 停止递归调用schedule...
        for (let i = 0; i < timers.length; i++) {
            if (timers[i] === this) {
                timers.splice(i--, 1);
            }
        }
    };

    zQuery.Fx.INTERVAL = 13;
    zQuery.Fx.start = function () {
        if (inProgress) {
            return;
        }

        inProgress = true;

        schedule();
    };

    function schedule() {
        if (inProgress) {
            if (window.requestAnimationFrame) {
                window.requestAnimationFrame(schedule);
            } else {
                setTimeout(schedule, zQuery.Fx.INTERVAL);
            }

            zQuery.Fx.tick();
        }
    }

    zQuery.Fx.tick = function () {
        let timer,
            i = 0;

        // 取出fx对象,执行step
        while (timer = timers[i++]) {
            timer.step();
        }

        if (!timers.length) {
            zQuery.Fx.stop();
        }
    };

    zQuery.Fx.stop = function () {
        inProgress = null;
    }

    // 添加常用动画
    zQuery.each({
        fadeIn: {opacity: "1", display: "block", special: true},
        fadeOut: {opacity: "0"}
    }, function (name, props) {
        zQuery.fn[name] = function (speed, easing, callback) {
            return this.animate(props, speed, easing, callback);
        }
    });

    /* 动画模块结束 */

    /* 其他的小工具 */
    const rtrim = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g;

    zQuery.trim = function (text) {
        return text == null ?
            "" :
            (text + "").replace(rtrim, "");
    }
    zQuery.now = Date.now;

    window.$ = window.zQuery = window.jQuery = zQuery;
})(window);